package container

import (
	"encoding/json"
	"errors"
	"os"
	"strconv"

	ps "github.com/keybase/go-ps"
	"golang.org/x/sys/unix"

	"cloudea.com/container/config"
	"cloudea.com/container/domain/image"
	"cloudea.com/container/utils"
)

/*
*
容器存取接口
*/
type Repository interface {
	/**
	获取所有容器
	*/
	GetContainers() []Container

	/**
	保存一个容器
	@param container 容器的信息
	*/
	Save(container Container) error

	/**
	删除一个容器
	@param name 容器的名称
	*/
	Delete(container Container) error

	/** 获取容器根文件系统位置 */
	GetFs(container Container) string

	/** 获取容器的所有名字空间 **/
	GetNs(container Container) []ContainerNamespace

	/** 获取容器的信息 */
	GetContainer(name string) (*Container, error)
}

type FileSystemRepository struct {
	/**
	数据库的位置
	*/
	home string
}

/** 通过名字获取容器 */
func (repo FileSystemRepository) GetContainer(name string) (*Container, error) {
	configFile := config.GetContainerConfig(name)
	if !utils.FileExists(configFile) {
		return nil, errors.New("配置文件不存在")
	}
	configFileInfo, err := os.Stat(configFile)
	if err != nil {
		return nil, err
	}

	content, err := os.ReadFile(configFile)
	if err != nil {
		return nil, err
	}

	var containerManifest ContainerManifest
	err = json.Unmarshal(content, &containerManifest)
	if err != nil {
		return nil, err
	}

	img, err := image.ImageGet(containerManifest.ImageName, containerManifest.ImageTag)
	if err != nil {
		return nil, err
	}

	// 通过检测进程是否运行来决定空器的状态
	var status ContainerStatus = CONTAINER_STOPPED
	if containerManifest.ThreadID != 0 {
		process, _ := ps.FindProcess(containerManifest.ThreadID)
		if process != nil {
			status = CONTAINER_RUNNING
		}
	}

	return &Container{
		Name:        containerManifest.Name,
		Image:       *img,
		Status:      status,
		CreatedTime: configFileInfo.ModTime(),
		ThreadId:    containerManifest.ThreadID,
		Command:     containerManifest.Command,
		Ip:          containerManifest.Ip,
		Hash:        containerManifest.Hash,
	}, nil
}

/*
*
获取所有容器
*/
func (repo FileSystemRepository) GetContainers() []Container {
	entry, err := os.ReadDir(config.CONTA_PATH)
	if err != nil {
		return nil
	}
	var containers []Container
	for _, file := range entry {
		if file.IsDir() {
			con, err := REPOSITORY.GetContainer(file.Name())
			if err == nil && con != nil {
				containers = append(containers, *con)
			}
		}
	}
	return containers
}

/*
*
保存一个容器
@param container 容器的信息
*/
func (repo FileSystemRepository) Save(container Container) error {
	containerBaseMeta := config.GetContainerConfig(container.Name)
	// 把配置信息保存到磁盘上
	manifest := ContainerManifest{
		Name:      container.Name,
		ImageName: container.Image.GetName(),
		ImageTag:  container.Image.GetTag(),
		Command:   container.Command,
		ThreadID:  container.ThreadId,
		Ip:        container.Ip,
		Hash:      container.Hash,
	}

	bytes, err := json.Marshal(manifest)
	if err != nil {
		panic(err)
	}

	err = utils.FileWriteBytes(containerBaseMeta, bytes)
	if err != nil {
		return err
	}

	return nil
}

/*
*
删除一个容器
@param name 容器的名称
*/
func (repo FileSystemRepository) Delete(container Container) error {
	containerBase := config.GetContainerBase(container.Name)
	containerMerged := containerBase + "/merged"
	containerRef := config.GetImageContainer(
		container.Image.GetName(),
		container.Image.GetTag(),
		container.Name,
	)
	// 0 删除cgroup
	if err := utils.Shell("cgdelete cpu:container/" + container.Name); err != nil {
		return err
	}

	// 1. 先unmount
	/** 重启后mount会消失， 对没有mount的执行umount会报错 */
	//unix.Unmount(containerMerged, unix.MOUNT)
	utils.Command("umount", "-l", containerMerged)

	// 2. 删除Base目录
	err := os.RemoveAll(containerBase)
	if err != nil {
		return err
	}
	// 3. 删除软链接
	err = os.RemoveAll(containerRef)
	if err != nil {
		return err
	}
	return nil
}

/** 获取一个容器的根文件系统 */
func (repo FileSystemRepository) GetFs(container Container) string {
	return config.GetContainerBase(container.Name) + "/merged"
}

/** 获取容器的所有名字空间 **/
func (repo FileSystemRepository) GetNs(container Container) []ContainerNamespace {
	var namespaceDir = "/proc/" + strconv.Itoa(container.ThreadId) + "/ns"
	var namespaces = []ContainerNamespace{
		{namespaceDir + "/cgroup", unix.CLONE_NEWCGROUP},
		{namespaceDir + "/ipc", unix.CLONE_NEWIPC},
		{namespaceDir + "/mnt", unix.CLONE_NEWNS},
		{namespaceDir + "/net", unix.CLONE_NEWNET},
		{namespaceDir + "/pid", unix.CLONE_NEWPID},
		{namespaceDir + "/time", unix.CLONE_NEWTIME},
		{namespaceDir + "/user", unix.CLONE_NEWUSER},
		{namespaceDir + "/uts", unix.CLONE_NEWUTS},
	}
	return namespaces
}

/** 容器的配置文件 */
type ContainerManifest struct {
	Name      string
	ImageName string
	ImageTag  string
	Command   []string
	ThreadID  int
	Ip        string
	Hash      string
}

/** 名字空间文件位置及其类型 */
type ContainerNamespace struct {
	Filename string
	Type     int
}

var REPOSITORY Repository = FileSystemRepository{config.HOME_PATH}
